package com.project.gobang.game;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.gobang.GobangApplication;
import com.project.gobang.model.UserInfo;
import com.project.gobang.service.UserService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.UUID;

@Data
// 这个类就表示一个游戏房间
public class Room {

    // 使用字符串类型来表示, 方便生成唯一值
    private String roomId;

    // 游戏房间内的玩家信息
    private UserInfo player1;
    private UserInfo player2;

    // 在房间中添加一个白手(先手方)
    private int WhiteUserId;

    // 引入 ObjectMapper 用于将 JS对象 转换成 Java对象
    private ObjectMapper objectMapper = new ObjectMapper();

    // 引入 OnlineUserManager 用于获取用户的 WebSocketSession
    private OnlineUserManager onlineUserManager;

    // 引入 RoomManager 用于将房间销毁
    private RoomManager roomManager;

    // 引入 UserService 用于更新用户信息(同样不能使用 Autowired, 需要从上下文中获取)
    private UserService userService;

    private static final int MAX_ROW = 15;
    private static final int MAX_COL = 15;
    // 使用一个二维数组来作为 "棋盘"
    // 0 表示当前位置没有子, 是空着的
    // 1 表示 玩家1 落的棋子
    // 2 表示 玩家2 落的棋子
    private int[][] board = new int[MAX_ROW][MAX_COL];

    // 在这个方法中, 要实现以下功能:
    public void putChess(String payload) throws IOException {
        // 1. 记录当前落子的位置.
        // 构建落子的Java对象请求, 接收来自客户端发来的 JSON 字符串的 JS 对象
        // 这就需要 objectMapper 来将 JS对象 转换成 Java对象
        PlaceChessRequest request = objectMapper.readValue(payload, PlaceChessRequest.class);
        // 构建落子响应, 方便随时返回
        PlaceChessResponse response = new PlaceChessResponse();

        // 当前棋子, 若 当前用户的id 等于 玩家1的id, 则设为1; 反之设为2.
        int chessOwner = request.getUserId() == player1.getUserId() ? 1 : 2;
        int row = request.getRow();
        int col = request.getCol();
        if (board[row][col] != 0) {
            // 虽然在客户端已经针对重复落子进行过判定了, 但是为了程序更加具有鲁棒性, 还是再在服务器判定一次
            System.out.println("当前位置 [" + row + "," + col + "] 已经有子了");
            return;
        }
        board[row][col] = chessOwner;

        // 打印棋盘内容
        printBoard();

        // 2. 进行胜负的判定
        int winner = winnerOwner(row, col, chessOwner);

        // 3. 给房间中的所有客户端返回响应
        response.setMessage("putChess");
        response.setUserId(request.getUserId());
        response.setRow(row);
        response.setCol(col);
        response.setWinner(winner);

        // 1) 要想给用户推送 Websocket 数据, 就要先拿到用户的会话(WebSocketSession(通过OnlineUserManager获取))
        WebSocketSession session1 = onlineUserManager.getUserOnlineStatusInRoomSession(player1.getUserId());
        WebSocketSession session2 = onlineUserManager.getUserOnlineStatusInRoomSession(player2.getUserId());
        // 不排除玩家在游戏过程中就退出了游戏
        if (session1 == null) {
            // 玩家1已经下线, 则认为玩家2获胜
            response.setWinner(player2.getUserId());
            System.out.println("玩家1掉线");
        }
        if (session2 == null) {
            // 玩家2已经下线, 则认为玩家1获胜
            response.setWinner(player1.getUserId());
            System.out.println("玩家2掉线");
        }

        // 把响应转换成 JSON 字符串
        String responseJson = objectMapper.writeValueAsString(response);
        if (session1 != null) {
            session1.sendMessage(new TextMessage(responseJson));
        }
        if (session2 != null) {
            session2.sendMessage(new TextMessage(responseJson));
        }


        // 4. 如果胜负已分, 就需要将房间销毁(从房间管理器中删除)
        if (winner != 0) {
            // 胜负已分, 更新玩家信息, 随后销毁房间
            // 4.1 对胜利的玩家和输掉的玩家进行信息更新
            int loseUserId = (winner == player1.getUserId() ? player2.getUserId() : player1.getUserId());
            userService.winTheGame(winner);
            userService.loseTheGame(loseUserId);

            System.out.println("胜负已分, 房间即将被销毁, 房间id为: " + roomId);
            roomManager.remove(roomId, player1.getUserId(), player2.getUserId());
        }
    }

    private void printBoard() {
        // 打印出棋盘
        System.out.println("【打印棋盘信息】 所属房间为：" + roomId);
        System.out.println("===============================");
        for (int row = 0; row < MAX_ROW; row++) {
            for (int col = 0; col < MAX_COL; col++) {
                System.out.print(board[row][col] + " ");
            }
            System.out.println();
        }
        System.out.println("===============================");
    }

    // 如果玩家1获胜, 则返回玩家1的id, 反之返回玩家2的id.
    // 若平局, 返回0
    private int winnerOwner(int row, int col, int chessOwner) {
        // 1. 检查行情况
        for (int curCol = col - 4; curCol <= col; curCol++) {
            // 以玩家下的棋坐标为原点, 检查该棋子的前四个位置和后四个位置, 如果能与该原点连成五子(颜色一致), 说明有玩家胜利了
            try {
                if (board[row][curCol] == chessOwner
                        && board[row][curCol+1] == chessOwner
                        && board[row][curCol+2] == chessOwner
                        && board[row][curCol+3] == chessOwner
                        && board[row][curCol+4] == chessOwner) {
                    // chessOwner 为1 说明是 玩家1 下的棋子
                    // chessOwner 为2 说明是 玩家2 下的棋子
                    // 如果 chessOwner 为1, 说明玩家1胜利, 返回玩家1的id, 反之返回玩家2的id
                    return chessOwner == 1 ? player1.getUserId() : player2.getUserId();
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                // 如果出现数组下标异常, 可以直接忽略, 对项目没有什么影响(大概)
                // 这是可预料的异常, 因此不作处理
                continue;
            }
        }

        // 2. 检查列的胜负情况
        for (int curRow = row - 4; curRow <= row; curRow++) {
            try {
                if (board[curRow][col] == chessOwner
                        && board[curRow+1][col] == chessOwner
                        && board[curRow+2][col] == chessOwner
                        && board[curRow+3][col] == chessOwner
                        && board[curRow+4][col] == chessOwner) {
                    return chessOwner == 1 ? player1.getUserId() : player2.getUserId();
                }
            }catch (ArrayIndexOutOfBoundsException e) {
                continue;
            }
        }

        // 3. 检查左对角线(和行列情况差不多, 左对角线就是检查斜率为-1的前四个棋子和后四个棋子)
        for (int curRow = row - 4, curCol = col - 4; curRow <= row && curCol <= col; curRow++, curCol++) {
            try {
                if (board[curRow][curCol] == chessOwner
                        && board[curRow + 1][curCol + 1] == chessOwner
                        && board[curRow + 2][curCol + 2] == chessOwner
                        && board[curRow + 3][curCol + 3] == chessOwner
                        && board[curRow + 4][curCol + 4] == chessOwner) {
                    return chessOwner == 1 ? player1.getUserId() : player2.getUserId();
                }
            }catch (ArrayIndexOutOfBoundsException e) {
                continue;
            }
        }

        // 4. 检查右对角线(还是以一个点为原点, 前四个棋子和后四个棋子的坐标范围, 循环遍历找出即可)
        // 画图出来就非常清晰
        for (int curRow = row - 4, curCol = col + 4; curRow <= row && curCol >= col; curRow++, curCol--) {
            try {
                if (board[curRow][curCol] == chessOwner
                        && board[curRow + 1][curCol - 1] == chessOwner
                        && board[curRow + 2][curCol - 2] == chessOwner
                        && board[curRow + 3][curCol - 3] == chessOwner
                        && board[curRow + 4][curCol - 4] == chessOwner) {
                    return chessOwner == 1 ? player1.getUserId() : player2.getUserId();
                }
            }catch (ArrayIndexOutOfBoundsException e) {
                continue;
            }
        }
        // 胜负未分返回0
        return 0;
    }

    public Room() {
        // 在构造 Room类 的时候, 就来生成唯一的字符串, 表示房间id.
        // 使用 UUID 来生成唯一值
        roomId = UUID.randomUUID().toString();

        // 通过入口类中记录的 context 来手动获取到 RoomManager 和 OnlineUserManager 的 Bean
        // RoomManager 和 OnlineUserManager 是以单例形式被Spring管理, 因此使用类名来获取它们的Bean
        onlineUserManager = GobangApplication.context.getBean(OnlineUserManager.class);
        roomManager = GobangApplication.context.getBean(RoomManager.class);
        userService = GobangApplication.context.getBean(UserService.class);
    }


}
